/****************************************************************************
 * Copyright 2009 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.filter;

import com.gotobject.filter.builders.FilteringBuilder;
import com.gotobject.filter.builders.OrderedFilteringBuilder;
import com.gotobject.filter.builders.RestrictedFilteringBuilder;
import com.gotobject.filter.command.Command;
import com.gotobject.filter.common.Predicates;
import com.gotobject.filter.internal.ToStrings;
import com.gotobject.filter.ordering.Ordering;
import com.gotobject.filter.predicate.Predicate;
import com.gotobject.filter.spi.ExecutionContext;
import com.gotobject.filter.spi.Filtering;
import com.gotobject.filter.spi.FilteringImpl;
import com.gotobject.filter.spi.TypeLiteral;
import com.gotobject.filter.transformation.Transformation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import static com.gotobject.filter.common.Iterables.asCollection;
import static com.gotobject.filter.common.Predicates.all;
import static com.gotobject.filter.common.Predicates.contains;
import static com.gotobject.filter.common.Predicates.not;
import static com.gotobject.filter.common.Predicates.take;
import static com.gotobject.filter.internal.Contracts.checkIfSelectKey;
import static com.gotobject.filter.internal.Contracts.checkIfWhereKey;
import static com.gotobject.filter.internal.Objects.asType;
import static com.gotobject.filter.internal.Pair.of;
import static com.gotobject.filter.internal.ToStrings.str;
import static java.util.Collections.emptyList;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 * @param <T> bound type.
 */
public final class RestrictedFilteringBuilderImpl<T> implements RestrictedFilteringBuilder<T> {
    private static final int                        FIRST_ELEMENT = 0;

    private final FilterImpl                        filter;
    private final Iterable<T>                       source;
    private final Filtering<T>                      filtering;

    private final CopyOnWriteArrayList<Predicate<? super T>>        predicates;
    private final CopyOnWriteArrayList<Ordering<T>>                 orderBys;

    /**
     * construct an implementation (sole one) of {@link RestrictedFilteringBuilder}
     * interface.
     * @param filter
     *      filter default implementation.
     * @param source
     *      {@code iterable} object to be filtered.
     */
    public RestrictedFilteringBuilderImpl(FilterImpl filter, Iterable<T> source){
        this.filter = filter;
        this.source = source;
        predicates = new CopyOnWriteArrayList<Predicate<? super T>>();
        orderBys    = new CopyOnWriteArrayList<Ordering<T>>();
        filtering   = new FilteringImpl<T>(filter);
    }

    @Override
    public <R> Iterable<R> apply(Transformation<? super R> key) {
        Iterable<R> r = emptyList();
        try {
            r = key.iterable(select());
        } catch (Exception e) {
           filter.addError(e);
        }

        filter.throwFailedFilteringExceptions();
        return r;
    }

    private Iterable <T> conditionalResult(Predicate<? super T> key){
        return isOrderBysEmpty()
                ? filtering.filter(key, walkingConditions())
                : walkingOrderBys(key);
    }

    private boolean isConditionsEmpty(){
        synchronized (predicates){
            return predicates.isEmpty() || predicates.contains(null);
        }
    }


    private boolean isOrderBysEmpty(){
        synchronized (orderBys){
            return orderBys.isEmpty();
        }
    }

    @Override
    public FilteringBuilder<T> orderBy(Ordering<? super T> order) {
        orderBys.addIfAbsent(asType(new TypeLiteral<Ordering<T>>(){}.getGenericClass(), order));
        return this;
    }


    @Override
    public Iterable<T> play(Command<? super T> command) {
        final Iterable<T> s = select();
        final Iterable<T> r = asType(new TypeLiteral<Iterable<T>>(){}.getGenericClass(),command.execute(s));
        if(!r.equals(s)) filter.addError("source and returned iterable disagree.");
        filter.throwFailedFilteringExceptions();
        return r;
    }


    private void reset(){
        resetOrderBys();
        resetConditions();
    }
    
    private void resetConditions(){
        synchronized (predicates){
            predicates.clear();
        }
    }
    
    private void resetOrderBys(){
        synchronized (orderBys){
            orderBys.clear();
        }
    }

    @Override
    public Iterable<T> select() {
        return select(all());
    }

    @Override
    public Iterable<T> select(int number) {
        final Predicate<T> c = ExecutionContext.select(take(number));
        return select(c);
    }

    @Override
    public Iterable<T> select(Predicate<? super T> key) {
        checkIfSelectKey(key, filter);
        try {
            final Iterable<T> result = isConditionsEmpty() ? asCollection(unconditionalResult(key)) : asCollection(conditionalResult(key));            
            filter.throwFailedFilteringExceptions();
            return result;
        } finally {
            reset();
        }

    }

    @Override    
    public Iterable<T> skip(int n) {
        final Predicate<T> s =  ExecutionContext.select(Predicates.skip(n));
        return select(s);
    }

    @Override
    public Iterable<T> skip(Predicate<? super T> key) {
        return select(not(contains(select(key))));
    }

    private Iterable <T> unconditionalResult(Predicate<? super T> key){
        return isOrderBysEmpty()
                ? walk(key, predicates, source)
                : walk(
                    orderBys.get(FIRST_ELEMENT),            // ordering function - usually the first one.
                    orderBys,                               // bulk of ordering functions.
                    filtering.filter(key, source)           // filtering function's call.
                  );
    }

    Iterable<T> walk(Ordering<T> key, List<Ordering<T>> orderBys, Iterable<T> src){
        Iterable<T> o = emptyList();
        try {
            o = key.of(src);
            orderBys.remove(key);
            final List<Ordering<T>> temp = new ArrayList<Ordering<T>>(orderBys);
            for(Ordering<T> each : temp){
                if(each.equals(key)) continue;
                o = walk(each, orderBys, o);
            }
        } catch (Throwable e){
            filter.addError(e);
        }
        return o;
    }


    Iterable<T> walk(Predicate<? super T> predicate, List<Predicate<? super T>> predicates, Iterable<T> src){
        Iterable<T> s = emptyList();
        try {
            s = filtering.filter(predicate, src);
            predicates.remove(predicate);
            final List<Predicate<? super T>> temp = new ArrayList<Predicate<? super T>>(predicates);
            for(Predicate<? super T> each : temp){
                if(null == each || each.equals(predicate)) continue;
                s = walk(each, predicates, s);
            }
        } catch (Throwable e){
            filter.addError(e);
        }

        return s;
    }

    private Iterable<T> walkingConditions(){
        return walk(predicates.get(FIRST_ELEMENT), predicates, source);
    }
    
    private Iterable<T> walkingOrderBys(Predicate<? super T> key){
        return walk(
            orderBys.get(FIRST_ELEMENT),            // ordering function - usually the first and only one.
            orderBys,                               // bulk ordering functions.
            filtering.filter(key, walkingConditions())
        );
    }

    @Override
    public OrderedFilteringBuilder<T> where(Predicate<? super T>... predicates) {
        checkIfWhereKey(filter, predicates);
        this.predicates.addAllAbsent(Arrays.asList(predicates));
        return this;
    }

    @Override
    public String toString() {
        return ToStrings.toString(
                this,
                of("orderBys", str(orderBys)),
                of("predicates",str(predicates))
        );
    }
}
